<template>
  <div class="upload-video">
    <el-upload
        v-model:file-list="fileList"
        :limit="props.videoLimit"
        :before-upload="beforeUpload"
        :http-request="httpRequest"
        :on-exceed="handleExceed"
        :on-remove="handleRemove"
    >
      <el-button type="primary">上传视频</el-button>
    </el-upload>

    <el-progress :text-inside="true" :stroke-width="24"
                 :percentage="uploadInfo.percentage" />

    <el-text>
      <el-icon><Odometer /></el-icon>
      上传速度: {{ uploadInfo.speed }}
    </el-text>

    <br/>

    <el-text>
      <el-icon><Timer /></el-icon>
      剩余时间: {{ uploadInfo.remainTime }}
    </el-text>
  </div>
</template>

<script lang="ts" setup>
import { computed, defineModel, defineProps, PropType, reactive, ref, watch } from 'vue'
import { ElMessage, UploadFile, UploadProps, UploadRawFile, UploadRequestOptions, UploadUserFile } from 'element-plus'
import { Timer, Odometer } from '@element-plus/icons-vue'
import { checkUploadFile, doUploadChunk, formatByte, formatTimes, mergeFileAip } from '@/utils/fileUtil.ts'
import { IChunkInfo } from '@/types'

const videoData = defineModel({
  required: true,
  type: [Array, String] as PropType<string | string[]>
})

const props = defineProps({
  videoPath: {
    required: true,
    type: String
  },
  videoLimit: {
    default: 3,
    type: Number
  },
  serviceFileRootPath: {
    required: true,
    type: String
  },
  serviceUrl: {
    required: true,
    type: String
  }
})

const fileList = ref<UploadUserFile[]>([])

const videoList = computed(() => {
  return Array.isArray(videoData.value) ? videoData.value : [videoData.value]
})

const updateVideoList = () => {
  fileList.value = []
  videoList.value.forEach((item) => {
    if (item) {
      fileList.value.push({
        name: 'video',
        url: `${props.serviceFileRootPath}${item}`
      })
    }
  })
}

watch(() => videoList.value, () => {
  updateVideoList()
}, {
  immediate: true
})

const uploadInfo = reactive({
  percentage: 0,
  beginTime: new Date(),
  speed: '-',
  remainTime: '-',
  filename: '',
  size: 0,
  path: ''
})

const chunkSize = 5 * 1024 * 1024
const chunkUrl = `${props.serviceUrl}/chunk`
const mergeUrl = `${props.serviceUrl}/chunk/merge`

const videoTypeList = ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime', 'video/x-msvideo']

const beforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
  return checkUploadFile(rawFile, videoTypeList, '视频', 1024)
}

const httpRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
  initUpload(options.file)

  const chunkCount = Math.ceil(uploadInfo.size / chunkSize)
  for (let offset = 0; offset < chunkCount; offset++) {
    await uploadChunkFile(offset, options.file)
  }

  await mergeFile()
}

const handleExceed: UploadProps['onExceed'] = () => {
  ElMessage.warning(`最多上传${props.videoLimit}个视频`)
}

const handleRemove: UploadProps['onRemove'] = (uploadFile: UploadFile) => {
  if (Array.isArray(videoData.value)) {
    const index = videoData.value.findIndex((value) => uploadFile.url === value)
    videoData.value.splice(index, 1)
  } else {
    videoData.value = ''
  }
}

/**
 * 初始化文件信息
 * @param file 文件
 */
const initUpload = (file: File) => {
  uploadInfo.percentage = 0
  uploadInfo.beginTime = new Date()
  uploadInfo.speed = '-'
  uploadInfo.filename = file.name
  uploadInfo.size = file.size
  uploadInfo.path = props.videoPath as string
}

/**
 * 上传文件块
 * @param offset 序号
 * @param file 文件
 */
const uploadChunkFile = async (offset: number, file: File) => {
  // 文件切片
  const startByte = offset * chunkSize
  const endByte = Math.min(uploadInfo.size, startByte + chunkSize)
  const chunkFile = file.slice(startByte, endByte)

  // 文件块信息 和服务器文件块信息对应 可自定义
  const chunkInfo: IChunkInfo = {
    chunkNumber: offset + 1,
    filename: uploadInfo.filename
  }

  await doUploadChunk(chunkUrl, offset, chunkInfo, chunkFile, uploadInfo.path, progressHandler)
}

/**
 * 合并文件
 */
const mergeFile = async () => {
  const result = await mergeFileAip(mergeUrl, uploadInfo.filename, uploadInfo.path)
  uploadInfo.percentage = 100

  if (Array.isArray(videoData.value)) {
    videoData.value.push(result.url)
  } else {
    videoData.value = result.url
  }
}

/**
 * 进度处理
 * @param event 进度事件
 * @param offset 当前文件块
 */
const progressHandler = (event: ProgressEvent, offset: number) => {
  const fileSize = uploadInfo.size
  const uploadSize = offset * chunkSize + event.loaded
  const remainSize = fileSize - uploadSize
  const percentage = Math.min(fileSize, uploadSize) / fileSize

  const timeDiff = new Date().getTime() - uploadInfo.beginTime.getTime()
  const secondDiff = Math.floor((timeDiff / 1000) % 60)

  if (secondDiff !== 0) {
    const speed = uploadSize / secondDiff
    uploadInfo.speed = `${formatByte(speed)}/s`
    uploadInfo.remainTime = formatTimes(Math.trunc(remainSize / speed))
  }

  if (percentage === 1) {
    uploadInfo.percentage = 99.99
  } else {
    uploadInfo.percentage = Number((percentage * 100).toFixed(2))
  }
}
</script>
